#include "Precompile.h"
#include "ModelLoader.h"

namespace Loading{
	ModelLoader::ModelLoader(){
	
	}

	Graphics::ModelPtr ModelLoader::load(){
		//m_filename =  Core::GeneralSettings::findFilePath(filename);
		m_inputStream.open(m_filename,std::ios_base::binary);

			// 		Data Order:
			//		//Model
			//			- DataHeader
			//			- Images
			//			- Effects
			//			- SubMeshHeaders
			//			- ModelHeaders
			//			- VertLayouts
			//			- VertLayoutElements
			//			- Vertices
			//			- Indices
			//		//Animation
			//			- Joints
			//			- Controllers
			//			- AnimationSources
			//			- Animations
			//			- AnimationSamplers
			//			- AnimationClips
			//			- AnimationFloatData
			//			- AnimationUintData

			m_inputStream.read((char*)&dataHeader,sizeof(dataHeader));

			// Model read
			textures = new outputTextureData[dataHeader.numTextures];
			m_inputStream.read((char*)textures,sizeof(outputTextureData)*dataHeader.numTextures);

			effects = new outputEffectData[dataHeader.numEffects];
			m_inputStream.read((char*)effects,sizeof(outputEffectData)*dataHeader.numEffects);

			submeshes = new outputSubMesh[dataHeader.numSubMeshes];
			m_inputStream.read((char*)submeshes,sizeof(outputSubMesh)*dataHeader.numSubMeshes);

			modelHeaders = new outputModelHeader[dataHeader.numModels];
			m_inputStream.read((char*)modelHeaders,sizeof(outputModelHeader)*dataHeader.numModels);

			layoutHeaders = new outputVertLayout[dataHeader.numVertLayouts];
			m_inputStream.read((char*)layoutHeaders,sizeof(outputVertLayout)*dataHeader.numVertLayouts);

			layoutElements = new outputVertLayoutElement[dataHeader.numVertLayoutElements];
			m_inputStream.read((char*)layoutElements,sizeof(outputVertLayoutElement)*dataHeader.numVertLayoutElements);

			// Allocate memory for buffers
			vertBuffer = new char[dataHeader.verticeBufferSize];
			indBuffer = new char[dataHeader.indexBufferSize];

			// Read content into buffers
			m_inputStream.read(vertBuffer,dataHeader.verticeBufferSize);
			m_inputStream.read(indBuffer,dataHeader.indexBufferSize);

			// Animation read
			joints = new outputJoint[dataHeader.numJoints];
			m_inputStream.read((char*)joints,sizeof(outputJoint)*dataHeader.numJoints);

			controllers = new outputController[dataHeader.numControllers];
			m_inputStream.read((char*)controllers,sizeof(outputController)*dataHeader.numControllers);

			animationSources = new outputAnimationSource[dataHeader.numAnimationSources];
			m_inputStream.read((char*)animationSources,sizeof(outputAnimationSource)*dataHeader.numAnimationSources);

			animations = new outputAnimation[dataHeader.numAnimations];
			m_inputStream.read((char*)animations,sizeof(outputAnimation)*dataHeader.numAnimations);

			animationSamplers = new outputAnimationSampler[dataHeader.numAnimationSamplers];
			m_inputStream.read((char*)animationSamplers,sizeof(outputAnimationSampler)*dataHeader.numAnimationSamplers);

			animationClips = new outputAnimationClip[dataHeader.numAnimationClips];
			m_inputStream.read((char*)animationClips,sizeof(outputAnimationClip)*dataHeader.numAnimationClips);

			animationFloats = new float[dataHeader.numAnimationFloats];
			m_inputStream.read((char*)animationFloats,sizeof(float)*dataHeader.numAnimationFloats);

			animationUints = new unsigned int[dataHeader.numAnimationUints];
			m_inputStream.read((char*)animationUints,sizeof(unsigned int)*dataHeader.numAnimationUints);

		m_inputStream.close();

		if(!processAnimations())
			return Graphics::ModelPtr();

		if(!processModels())
			return Graphics::ModelPtr();

		cleanUp();
		return m_models.back();
	}

	bool ModelLoader::processAnimations(){
		Graphics::AnimationManager* animMgr = Graphics::GraphicsFactory::m_renderContext->m_animationManager;
		std::map<unsigned int,unsigned int> animationIdMap;

		unsigned int sourceOffset = 0;
		Graphics::AnimationSamplerPtr samplerPtr;
		std::map<Graphics::AnimationSampler::AnimationSamplingType,std::vector<float>> samplerData;
		std::map<Graphics::AnimationSampler::AnimationSamplingType,unsigned int> samplerStrides;

		for(unsigned int i=0; i<dataHeader.numAnimations; i++){
			samplerData.clear();
			samplerStrides.clear();

			sourceOffset = animationSamplers[animations[i].m_samplerId].m_sourceOffset;
			for(unsigned int j=0; j<animationSamplers[animations[i].m_samplerId].m_numSources; j++){
				if(!processAnimationPart(sourceOffset+j,samplerData,samplerStrides))
					return false;
			}

			Graphics::AnimationSampler::AnimationTargetComponent compType;
			switch(animations[i].m_targetComponent){
			case outputAnimation::TC_TRANSLATE_X :
				compType = Graphics::AnimationSampler::ATC_TRANSLATION_X;
				break;
			case outputAnimation::TC_TRANSLATE_Y :
				compType = Graphics::AnimationSampler::ATC_TRANSLATION_Y;
				break;
			case outputAnimation::TC_TRANSLATE_Z :
				compType = Graphics::AnimationSampler::ATC_TRANSLATION_Z;
				break;
			case outputAnimation::TC_SCALE_X :
				compType = Graphics::AnimationSampler::ATC_SCALE_X;
				break;
			case outputAnimation::TC_SCALE_Y :
				compType = Graphics::AnimationSampler::ATC_SCALE_Y;
				break;
			case outputAnimation::TC_SCALE_Z :
				compType = Graphics::AnimationSampler::ATC_SCALE_Z;
				break;
			case outputAnimation::TC_ORIENTATION_X :
				compType = Graphics::AnimationSampler::ATC_ORIENTATION_X;
				break;
			case outputAnimation::TC_ORIENTATION_Y :
				compType = Graphics::AnimationSampler::ATC_ORIENTATION_Y;
				break;
			case outputAnimation::TC_ORIENTATION_Z :
				compType = Graphics::AnimationSampler::ATC_ORIENTATION_Z;
				break;
			case outputAnimation::TC_VISIBILITY :
				compType = Graphics::AnimationSampler::ATC_VISIBILITY;
				break;
			case outputAnimation::TC_TRANSFORM :
				compType = Graphics::AnimationSampler::ATC_TRANSFORM;
				break;
			default:
				break;
			}

			samplerPtr = Graphics::AnimationSamplerPtr(new Graphics::AnimationSampler(samplerData,samplerStrides,animations[i].m_targetJoint,compType));
			animationIdMap[i] = animMgr->addAnimationSampler(samplerPtr);
		}

		unsigned int animIdOffset = 0;
		std::vector<unsigned int> animIds;
		for(unsigned int i=0; i<dataHeader.numAnimationClips; i++){
			animIds.clear();
			animIdOffset = animationClips[i].m_animIdOffset;
			for(unsigned int j=0; j<animationClips[i].m_numAnimIds; j++){
				animIds.push_back(animationIdMap[animationUints[animIdOffset+j]]);
			}

			Graphics::AnimationClipPtr clip = Graphics::AnimationClipPtr(new Graphics::AnimationClip(animIds,animationClips[i].m_start,animationClips[i].m_end,std::string(animationClips[i].m_name)));
			m_clipsAdded.push_back(std::pair<std::string,unsigned int>(std::string(animationClips[i].m_name),animMgr->addAnimationClip(clip)));
		}
		return true;
	}

	bool ModelLoader::processAnimationPart(	unsigned int sourceId, 
											std::map<Graphics::AnimationSampler::AnimationSamplingType,std::vector<float>>& samplerData,
											std::map<Graphics::AnimationSampler::AnimationSamplingType,unsigned int>& samplerStrides){
		std::vector<float> floatVec;
		std::vector<unsigned int> uintVec;

		switch(animationSources[sourceId].m_type){
		case outputAnimationSource::AST_FLOAT:
			floatVec.assign(animationSources[sourceId].m_dataCount,0);
			std::copy(&animationFloats[animationSources[sourceId].m_dataOffset],(&animationFloats[animationSources[sourceId].m_dataOffset])+animationSources[sourceId].m_dataCount,floatVec.data());
			break;
		case outputAnimationSource::AST_STRING:
			LOG_ERROR("Unsupported.");
			assert(0);
			break;
		case outputAnimationSource::AST_UINT:
			uintVec.assign(animationSources[sourceId].m_dataCount,0);
			std::copy(&animationUints[animationSources[sourceId].m_dataOffset],(&animationUints[animationSources[sourceId].m_dataOffset])+animationSources[sourceId].m_dataCount,uintVec.data());
			break;
		default:
			break;
		}

		switch(animationSources[sourceId].m_semantic){
		case outputAnimationSource::ASS_INPUT:
			samplerData[Graphics::AnimationSampler::AST_INPUT] = floatVec;
			samplerStrides[Graphics::AnimationSampler::AST_INPUT] = animationSources[sourceId].m_stride;
			break;
		case outputAnimationSource::ASS_INTERPOLATION:
			for(unsigned int i=0; i<uintVec.size(); i++){
				switch(uintVec[i]){
				case outputAnimationSampler::SIT_BEIZER:
					floatVec.push_back(Graphics::AnimationSampler::AIT_BEZIER);
					break;
				case outputAnimationSampler::SIT_STEP:
					floatVec.push_back(Graphics::AnimationSampler::AIT_STEP);
					break;
				case outputAnimationSampler::SIT_BSPLINE:
				case outputAnimationSampler::SIT_CARDINAL:
				case outputAnimationSampler::SIT_HERMITE:
					LOG_ERROR("Unsupported interpolation type");
					break;
				case outputAnimationSampler::SIT_LINEAR:
					floatVec.push_back(Graphics::AnimationSampler::AIT_LINEAR);
					break;
				}
			}

			samplerData[Graphics::AnimationSampler::AST_INTERPOLATION] = floatVec;
			samplerStrides[Graphics::AnimationSampler::AST_INTERPOLATION] = animationSources[sourceId].m_stride;
			break;
		case outputAnimationSource::ASS_IN_TANGENT:
			samplerData[Graphics::AnimationSampler::AST_IN_TANGENT] = floatVec;
			samplerStrides[Graphics::AnimationSampler::AST_IN_TANGENT] = animationSources[sourceId].m_stride;
			break;
		case outputAnimationSource::ASS_OUTPUT:
			samplerData[Graphics::AnimationSampler::AST_OUTPUT] = floatVec;
			samplerStrides[Graphics::AnimationSampler::AST_OUTPUT] = animationSources[sourceId].m_stride;
			break;
		case outputAnimationSource::ASS_OUT_TANGENT:
			samplerData[Graphics::AnimationSampler::AST_OUT_TANGENT] = floatVec;
			samplerStrides[Graphics::AnimationSampler::AST_OUT_TANGENT] = animationSources[sourceId].m_stride;
			break;
		default:
			break;
		}

		return true;
	}

	bool ModelLoader::processModels(){
		Graphics::VertexDataPtr vertData = Graphics::VertexDataPtr(new Graphics::VertexData);
		Graphics::IndexDataPtr indData = Graphics::IndexDataPtr(new Graphics::IndexData);
		outputSubMesh* subMesh;
		std::vector<Graphics::SubModelMeshPtr> subModelMeshes;
		Graphics::SubModelMeshPtr subModelMeshPtr;
		std::vector<unsigned int> meshesConcatenated;
		unsigned int ful = 0;
		for(unsigned int i=0; i<dataHeader.numModels; i++){
			m_models.push_back(Graphics::ModelPtr(new Graphics::Model(m_filename)));

			// Create controller
			if(modelHeaders[i].m_controllerId != -1){
				AnimationControllerPtr controllerPtr;
				std::vector<Graphics::SkeletonJointPtr> jointVec;
				Utility::Math::Matrix<4,4> bindMatrix;
				
				unsigned int jOffset = controllers[modelHeaders[i].m_controllerId].m_jointOffset;
				for(unsigned int j=0; j<controllers[modelHeaders[i].m_controllerId].m_numJoints; j++){
					jointVec.push_back(Graphics::SkeletonJointPtr(new Graphics::SkeletonJoint));
					jointVec.back()->m_name = joints[jOffset+j].m_name;
					jointVec.back()->m_invBindMatrix = joints[jOffset+j].m_bindInvMatrix;
					jointVec.back()->m_invBindMatrix.transpose();
					jointVec.back()->m_transformMatrix = joints[jOffset+j].m_transformMatrix;
					jointVec.back()->m_transformMatrix.transpose();
					jointVec.back()->m_bakedTransform = joints[jOffset+j].m_bakedTransform;
					std::copy(joints[jOffset+j].m_translate,joints[jOffset+j].m_translate+3,jointVec.back()->m_translate.a);
					std::copy(joints[jOffset+j].m_scale,joints[jOffset+j].m_scale+3,jointVec.back()->m_scale.a);
					std::copy(&joints[jOffset+j].m_rotateX[0],&joints[jOffset+j].m_rotateX[0]+24,&jointVec.back()->m_rotateAngleX.a[0]);

					jointVec[j]->m_parent = joints[jOffset+j].m_parentId;

					std::vector<unsigned int> childVec;
					unsigned int childIdOffset = joints[jOffset+j].m_childIdOffset;
					for(unsigned int k=0; k<joints[jOffset+j].m_numChilds; k++){
						childVec.push_back(animationUints[childIdOffset+k]);
					}

					if(!childVec.empty())
						jointVec[j]->m_children = childVec;
				}

				bindMatrix = controllers[modelHeaders[i].m_controllerId].m_bindMatrix;
				bindMatrix.transpose();
				controllerPtr = AnimationControllerPtr(new Graphics::AnimationController(jointVec,bindMatrix,std::string(controllers[modelHeaders[i].m_controllerId].m_name)));

				// Add all clips for now
				for(unsigned int j=0; j<m_clipsAdded.size(); j++){
					controllerPtr->addAnimationClip(m_clipsAdded[j].first,m_clipsAdded[j].second);
				}

				m_models.back()->addController(Graphics::GraphicsFactory::m_renderContext->m_animationManager->addAnimationController(controllerPtr));
			}

			for(unsigned int j=0; j<20; j++){
				if(modelHeaders[i].m_subMeshIds[j] == -1)
					break;
				subMesh = &submeshes[modelHeaders[i].m_subMeshIds[j]];
				subModelMeshes.push_back(Graphics::SubModelMeshPtr(new Graphics::SubModelMesh));
				subModelMeshPtr = subModelMeshes.back();
				
				// Concat if viable
				if(j != 0){
					if(subMesh->m_vertLayoutId == submeshes[modelHeaders[i].m_subMeshIds[j-1]].m_vertLayoutId){
						// Increase ind values
						unsigned int *indIncrement = (unsigned int*)indBuffer;
						for(unsigned int k=0; k<subMesh->m_numInds; k++){
							indIncrement[subMesh->m_indOffset+k] += vertData->m_numVerts;
						}

						meshesConcatenated.push_back(modelHeaders[i].m_subMeshIds[j]);
						indData->m_numInds += subMesh->m_numInds;
						vertData->m_numVerts += subMesh->m_numVerts;
					}
					else{
						// Create vert buffer
						vertData->m_data = vertBuffer+submeshes[*meshesConcatenated.begin()].m_vertOffset;
						unsigned int vertBufferId = m_models.back()->addVertexBuffer(Graphics::GraphicsFactory::createVertexBuffer(vertData));

						for(unsigned int k=0; k<meshesConcatenated.size(); k++){
							subModelMeshes[meshesConcatenated[k]]->m_vertBuffer = vertBufferId;
						}
						
						meshesConcatenated.clear();

						vertData->m_byteSize = subMesh->m_vertDataStride;
						vertData->m_numVerts = subMesh->m_numVerts;
						indData->m_numInds += subMesh->m_numInds;
					}
				}
				else{
					vertData->m_byteSize = subMesh->m_vertDataStride;
					vertData->m_numVerts = subMesh->m_numVerts;

					indData->m_byteSize = subMesh->m_indDataStride;
					indData->m_data = indBuffer;
					indData->m_numInds = subMesh->m_numInds;

					meshesConcatenated.push_back(modelHeaders[i].m_subMeshIds[j]);
				}

				// Create material
				outputEffectData &data = effects[subMesh->m_effectId];
				Graphics::MaterialPtr material = Graphics::MaterialPtr(new Graphics::Material);
				memcpy(material->m_colorAmbient.a,data.m_ambient,sizeof(float)*4);
				memcpy(material->m_colorDiffuse.a,data.m_diffuse,sizeof(float)*4);
				memcpy(material->m_colorEmissive.a,data.m_emissive,sizeof(float)*4);
				memcpy(material->m_colorSpecular.a,data.m_specular,sizeof(float)*4);
				memcpy(material->m_colorTransparent.a,data.m_transparent,sizeof(float)*4);
				material->m_colorTransparency = data.m_transparency;
				material->m_colorShininess = data.m_shininess;

				bool hasNormalMap = false, hasDiffuseMap = false, hasSpecularMap = false, skinned = false;

				// Create textures
				for(unsigned int k=0; k<10; k++){
					if(effects[subMesh->m_effectId].m_textureIds[k] == -1)
						break;

					std::string fileName = textures[effects[subMesh->m_effectId].m_textureIds[k]].m_filename;
					
					// TODO: Check if it exists!

					Graphics::Texture2D::ShaderResourceSlot slot;
					switch(textures[effects[subMesh->m_effectId].m_textureIds[k]].m_channel){
					case TC_DIFFUSE:
						slot = Graphics::Texture2D::SRS_DIFFUSE;
						hasDiffuseMap = true;
						break;
					case TC_BUMP:
						slot = Graphics::Texture2D::SRS_NORMALMAP;
						hasNormalMap = true;
						break;
					case TC_SPECULAR:
						slot = Graphics::Texture2D::SRS_SPECULAR;
						hasSpecularMap = true;
						break;
					default:
						LOG_ERROR("Undefined shader resourde slot.");
						assert(0);
						cleanUp();
						return false;
					}

					material->m_textureIds.push_back(Graphics::GraphicsFactory::m_renderContext->m_materialManager->addTexture2D(Graphics::GraphicsFactory::createTexture2D(fileName,slot)));
				}

				outputVertLayout &lay = layoutHeaders[subMesh->m_vertLayoutId];
				for(unsigned int i=0; i<10; i++){
					if(lay.m_elementIds[i] == -1)
						break;

					std::string layElemName = layoutElements[lay.m_elementIds[i]].m_semantic;
					if(layElemName.compare("JOINT") == 0)
						skinned = true;
				}

				std::string materialName = std::string(data.m_name);
				std::vector<std::string> nameParts;
				// Check name for hints about what shader to use
				while(materialName.find_first_of('_') != std::string::npos){
					nameParts.push_back(materialName.substr(0,materialName.find_first_of('_')));
					materialName = materialName.substr(materialName.find_first_of('_')+1,materialName.size());
				}
				nameParts.push_back(materialName);

				material->m_name = m_filename.substr(m_filename.find_last_of('/')+1,m_filename.size())+"_"+nameParts[0];
				if(nameParts.size() >= 2)
					material->m_vertexShaderId = Graphics::GraphicsFactory::m_renderContext->m_materialManager->getVertexShaderIdByName("VS_"+nameParts[nameParts.size()-2]);
				if(nameParts.size() >= 3)
					material->m_pixelShaderId = Graphics::GraphicsFactory::m_renderContext->m_materialManager->getPixelShaderIdByName("PS_"+nameParts[nameParts.size()-1]);

				if(material->m_vertexShaderId == -1){
					if(hasNormalMap){
						if(skinned)
							material->m_vertexShaderId = Graphics::GraphicsFactory::m_renderContext->m_materialManager->getVertexShaderIdByName(std::string("VS_NormalMappingSkinned"));
						else
							material->m_vertexShaderId = Graphics::GraphicsFactory::m_renderContext->m_materialManager->getVertexShaderIdByName(std::string("VS_NormalMapping"));
					}
					else{
						if(skinned)
							material->m_vertexShaderId = Graphics::GraphicsFactory::m_renderContext->m_materialManager->getVertexShaderIdByName(std::string("VS_DefaultSkinned"));
						else
							material->m_vertexShaderId = Graphics::GraphicsFactory::m_renderContext->m_materialManager->getVertexShaderIdByName(std::string("VS_Default"));
					}
				}
				if(material->m_pixelShaderId == -1){
					if(hasNormalMap)
						material->m_pixelShaderId = Graphics::GraphicsFactory::m_renderContext->m_materialManager->getPixelShaderIdByName(std::string("PS_NormalMapping"));
					else
						material->m_pixelShaderId = Graphics::GraphicsFactory::m_renderContext->m_materialManager->getPixelShaderIdByName(std::string("PS_Default"));
				}

				std::string comparator;
				for(unsigned int k=0; k<10; k++){
					if(layoutHeaders[subMesh->m_vertLayoutId].m_elementIds[k] == -1)
						break;

					std::string sem = layoutElements[layoutHeaders[subMesh->m_vertLayoutId].m_elementIds[k]].m_semantic;
					comparator += sem;
				}

				subModelMeshPtr->m_materialId = Graphics::GraphicsFactory::m_renderContext->m_materialManager->addMaterial(material);
				subModelMeshPtr->m_indOffset = subMesh->m_indOffset;
				subModelMeshPtr->m_nuInds = subMesh->m_numInds;
			}

			// Create last vertex buffer
			vertData->m_data = vertBuffer+submeshes[*meshesConcatenated.begin()].m_vertOffset*sizeof(float);
			unsigned int vertBufferId = m_models.back()->addVertexBuffer(Graphics::GraphicsFactory::createVertexBuffer(vertData));

			for(unsigned int k=0; k<meshesConcatenated.size(); k++){
				subModelMeshes[meshesConcatenated[k]]->m_vertBuffer = vertBufferId;
			}
			
			meshesConcatenated.clear();

			// Add submeshes
			for(unsigned int j=0; j<subModelMeshes.size(); j++){
				m_models.back()->addSubModel(subModelMeshes[j]);
			}
			m_models.back()->setIndBuffer(Graphics::GraphicsFactory::createIndexBuffer(indData));
		}

		return true;
	}

	void ModelLoader::cleanUp(){
		// Free up temporary buffer memory.
		delete[] vertBuffer;
		delete[] indBuffer;
		delete[] textures;
		delete[] effects;
		delete[] submeshes;
		delete[] modelHeaders;
		delete[] layoutHeaders;
		delete[] layoutElements;

		delete[] controllers;
		delete[] joints;
		delete[] animations;
		delete[] animationSamplers;
		delete[] animationClips;
		delete[] animationSources;
		delete[] animationFloats;
		delete[] animationUints;
	}
}